package main

import (
	"bytes"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

const (
	writeWait      = 10 * time.Second
	pongWait       = 60 * time.Second    // 客户端浏览器定时发送心跳信息
	pingPeriod     = (pongWait * 9) / 10 // 服务端定时发送心跳信息
	maxMessageSize = 512
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024, // 读缓冲大小
	WriteBufferSize: 1024, // 写缓冲大小
}

type Client struct {
	hub  *Hub
	conn *websocket.Conn
	send chan []byte
}

// 处理浏览器发送的信息,同步到集线器hub.broadcast
func (c *Client) readPump() {
	fmt.Println("client readPump run 01")
	defer func() {
		c.hub.unregister <- c
		fmt.Printf("close connection to %s\n", c.conn.RemoteAddr().String())
		c.conn.Close()
	}()

	// c.conn 连接管理
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	// 读取浏览器发送过来的信息,同步到hub.broadcase管道
	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("error: %v", err)
			}
			break //只要ReadMessage失败，就关闭websocket管道、注销client，退出
		}
		// 换行符用空格替代，bytes.TrimSpace把首尾连续的空格去掉，-1为全部替换,
		// bytes.Trimspace切掉前后空格
		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		c.hub.broadcast <- message
	}
}

// 把c.send管道的信息,发送给浏览器显示
func (c *Client) writePump() {
	fmt.Println("client writePump run 01")
	// 定时任务队列
	ticket := time.NewTicker(pingPeriod)

	defer func() {
		ticket.Stop()
		fmt.Printf("close connection to %s\n", c.conn.RemoteAddr().String())
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				log.Println("client send chan已经关闭")
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(message)

			// 有消息一次全写出去
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				w.Write(<-c.send)
			}

			// 必须调close，否则下次调用client.conn.NextWriter时本条消息才会发送给浏览器
			if err := w.Close(); err != nil {
				return
			}
		case <-ticket.C: // 每隔pingPeriod时间,就发送ping心跳消息给客户端浏览器
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
	fmt.Println("client serveWs run")
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
	client.hub.register <- client
	fmt.Println("serveWS client: ", client)

	go client.readPump()
	fmt.Println("client readPump run")
	go client.writePump()
	fmt.Println("client writePump run")
}
